#!/bin/bash
# Build docker image
# Copyright (c) MarsBighead, 2024. All rights reserved.
#
#openGauss is licensed under Mulan PSL v2.
#You can use this software according to the terms and conditions of the Mulan PSL v2.
#You may obtain a copy of Mulan PSL v2 at:
#
#          http://license.coscl.org.cn/MulanPSL2
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.
#-------------------------------------------------------------------------
#
# entrypint.sh
#    Build docker image
#
# IDENTIFICATION
#    phant-container/images/entrypint.sh
#
#-------------------------------------------------------------------------

# set -eu
set -Eeo pipefail

declare GAUSSDATA=${GAUSSDATA:-/opt/data}

# Suport recovery from env variables
declare GS_PORT=${GS_PORT:-5432}
# GS_USER is initial user 
declare GS_USER=${GS_USER:-omm}

# declare GS_REPLUSER=${GS_REPLUSER:-sufe}
# GS_USERNAME is sysadmin user with remote access,
#             and it can be empty if no remote access requirements 
# declare GS_USERNAME=${GS_USERNAME:-hbu}

# required by initializing openGauss standby instance 
declare max_retry_primary_num=${max_retry_primary_num:-10}
declare max_retry_primary_interval=${max_retry_primary_interval:-10}
declare max_delay_primary_interval=${max_delay_primary_interval:-30}



current_dir=$(cd $(dirname $0); pwd)
cd ${current_dir}
source /etc/profile
#source lib.sh

function file_env() {
    local var="$1"
    local fileVar="${var}_FILE"
    local def="${2:-}"
    if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
        echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
        exit 1
    fi
    local val="$def"
    if [ "${!var:-}" ]; then
        val="${!var}"
    elif [ "${!fileVar:-}" ]; then
        val="$(< "${!fileVar}")"
    fi
    export "$var"="$val"
    unset "$fileVar"
}

# check to see if this file is being run or sourced from another script
function _is_sourced() {
    [ "${#FUNCNAME[@]}" -ge 2 ] \
       && [ "${FUNCNAME[0]}" = '_is_sourced' ] \
       && [ "${FUNCNAME[1]}" = 'source' ]
}

########################
## Init OpenGauss ENV ##
########################

# Loads various settings that are used elsewhere in the script
# This should be called before any other functions
function docker_setup_env() {

    file_env 'GS_PASSWORD'
 
    # file_env 'GS_USER' 'omm'
    file_env 'GS_DB' "$GS_USER"
    file_env 'POSTGRES_INITDB_ARGS'
    # default authentication method is md5
    : "${GS_HOST_AUTH_METHOD:=md5}"
    if [ $(uname) == "Linux" ]; then 
        declare -g DATABASE_ALREADY_EXISTS
    fi
    # look specifically for OG_VERSION, as it is expected in the DB dir
    if [ -s "$PGDATA/PG_VERSION" ]; then
       DATABASE_ALREADY_EXISTS='true'
    fi
}

# USER level env variables 
function set_envfile() {

    export ENVFILE=${ENVFILE:-/home/omm/.bashrc}
    export USER=omm
    export GROUP=omm
    # GAUSS_EDITION only in patch phase
    #export GAUSS_EDITION=lite

    #export GAUSSHOME=${GAUSSHOME}
    # PGDATA is the default path to store openGuass data for instance 
    export PGDATA=${PGDATA:-${GAUSSDATA}/openGauss}
    
    # GAUSSHOME has been assigned value in Dockerfile 
    export GAUSSLOG=${GAUSSLOG:-${GAUSSDATA}/gausslog}
    export PGHOST=${GAUSSLOG:-${GAUSSDATA}/tmp}
    export PATH=${PATH}:${GAUSSHOME}/bin
    export LD_LIBRARY_PATH=${GAUSSHOME}/lib:${LD_LIBRARY_PATH}

    
    #GPHOME: tool path, used by openGauss-OM
    if [ $(grep $PATH ~/.bashrc  | grep $GAUSSHOME | wc -l) -gt 0 ];then
        source ${ENVFILE}
    else
    cat >>  ${ENVFILE} <<EOF 
export GAUSSHOME=${GAUSSHOME}
export GAUSSDATA=${GAUSSDATA}
export PGDATA=${PGDATA}
export GAUSSLOG=${GAUSSLOG}
export PGHOST=${PGHOST}

# lib variables under local user
export PATH=${PATH}:${GAUSSHOME}/bin
export LD_LIBRARY_PATH=${GAUSSHOME}/lib:${LD_LIBRARY_PATH}

export PYTHONPATH=${PYTHONPATH}

export GAUSS_ENV=2
EOF
    fi 
#HOST INFO" 
#export PRIMARYHOST=${primary_host} 
#export STANDBYHOSTS=${standby_hosts}
#export PRIMARYNAME=${primary_name}
#export STANDBYNAMES=${standby_names}
    
}

# Execute sql script, passed via stdin (or -f flag of pqsl)
# usage: docker_process_sql [gsql-cli-args]
#    ie: docker_process_sql --dbname=mydb <<<'INSERT ...'
#    ie: docker_process_sql -f my-file.sql
#    ie: docker_process_sql <my-file.sql
function docker_process_sql() {
    local query_runner=( gsql -v ON_ERROR_STOP=1 --username "$GS_USER" --password "$GS_PASSWORD")
    if [ -n "$GS_DB" ]; then
        query_runner+=( --dbname "$GS_DB" )
    fi
    if [ -n "$GS_PORT" ]; then
        query_runner+=( --port "$GS_PORT" )
    fi

    echo "Execute SQL: ${query_runner[@]} $@"
    "${query_runner[@]}" "$@"
}

####################
## Init OpenGauss ##
####################

# print large warning if GS_PASSWORD is long
# error if both GS_PASSWORD is empty and GS_HOST_AUTH_METHOD is not 'trust'
# print large warning if GS_HOST_AUTH_METHOD is set to 'trust'
# assumes database is not set up, ie: [ -z "$DATABASE_ALREADY_EXISTS" ]
function docker_verify_minimum_env() {
    # check password first so we can output the warning before postgres
    # messes it up
    if [[ "$GS_PASSWORD" =~  ^(.{8,}).*$ ]] &&  [[ "$GS_PASSWORD" =~ ^(.*[a-z]+).*$ ]] && [[ "$GS_PASSWORD" =~ ^(.*[A-Z]).*$ ]] &&  [[ "$GS_PASSWORD" =~ ^(.*[0-9]).*$ ]] && [[ "$GS_PASSWORD" =~ ^(.*[#?!@$%^&*-]).*$ ]]; then
            cat >&2 <<-'EOWARN'

        Message: The supplied GS_PASSWORD is meet requirements.

EOWARN
    else
             cat >&2 <<-'EOWARN'

        Error: The supplied GS_PASSWORD is not meet requirements.
        Please Check if the password contains uppercase, lowercase, numbers, special characters, and password length(8).
        At least one uppercase, lowercase, numeric, special character.
        Example: Enmo@123
EOWARN
        exit 1
    fi
    if [ -z "$GS_PASSWORD" ] && [ 'trust' != "$GS_HOST_AUTH_METHOD" ]; then
        # The - option suppresses leading tabs but *not* spaces. :)
        cat >&2 <<-'EOE'
        Error: Database is uninitialized and superuser password is not specified.
               You must specify GS_PASSWORD to a non-empty value for the
               superuser. For example, "-e GS_PASSWORD=password" on "docker run".

               You may also use "GS_HOST_AUTH_METHOD=trust" to allow all
               connections without a password. This is *not* recommended.

EOE
        exit 1
    fi
    if [ 'trust' = "$GS_HOST_AUTH_METHOD" ]; then
        cat >&2 <<-'EOWARN'
        ********************************************************************************
        WARNING: GS_HOST_AUTH_METHOD has been set to "trust". This will allow
                 anyone with access to the opengauss port to access your database without
                 a password, even if GS_PASSWORD is set.
                 It is not recommended to use GS_HOST_AUTH_METHOD=trust. Replace
                 it with "-e GS_PASSWORD=password" instead to set a password in
                 "docker run".
        ********************************************************************************
EOWARN
    fi
}

function _opengauss_want_help() {
    local arg
    count=1
    for arg; do
        case "$arg" in
        # postgres --help | grep 'then exit'
        # leaving out -C on purpose since it always fails and is unhelpful:
        # postgres: could not access the server configuration file "/var/lib/postgresql/data/postgresql.conf": No such file or directory
            -'?'|--help|--describe-config|-V|--version)
               return 0
                ;;
            esac
        if [ "$arg" == "-M" ]; then
            SERVER_MODE=${@:$count+1:1}
            echo "openGauss DB SERVER_MODE = $SERVER_MODE"
            shift
        fi
        count=$[$count + 1]
    done
    return 1
}

# initialize empty PGDATA directory with new database via 'initdb'
# arguments to `initdb` can be passed via POSTGRES_INITDB_ARGS or as arguments to this function
# `initdb` automatically creates the "postgres", "template0", and "template1" dbnames
# this is also where the database user is created, specified by `GS_USER` env
function docker_init_database() {
    # "initdb" is particular about the current user existing in "/etc/passwd", so we use "nss_wrapper" to fake that if necessary
    if ! getent passwd "$(id -u)" &> /dev/null && [ -e /usr/lib/libnss_wrapper.so ]; then
        export LD_PRELOAD='/usr/lib/libnss_wrapper.so'
        export NSS_WRAPPER_PASSWD="$(mktemp)"
        export NSS_WRAPPER_GROUP="$(mktemp)"
        echo "postgres:x:$(id -u):$(id -g):PostgreSQL:$PGDATA:/bin/false" > "$NSS_WRAPPER_PASSWD"
        echo "postgres:x:$(id -g):" > "$NSS_WRAPPER_GROUP"
    fi

    if [ -n "$POSTGRES_INITDB_XLOGDIR" ]; then
        set -- --xlogdir "$POSTGRES_INITDB_XLOGDIR" "$@"
    fi
    if [ -z "$GS_NODENAME" ]; then
        GS_NODENAME=gaussdb
    fi 
    # eval 'gs_initdb --pwfile=<(echo "$GS_PASSWORD") --nodename=gaussdb '"$POSTGRES_INITDB_ARGS"' "$@"'
    cat <<EOF
        GS_NODENAME=$GS_NODENAME 
        'gs_initdb  --pwpasswd=GS_PASSWORD --nodename=$GS_NODENAME -D $PGDATA"'
EOF
    # --pwfile should input a PATH or DIRECTORY value, and streaming content is invalid
    eval 'gs_initdb --pwpasswd=${GS_PASSWORD} --nodename=$GS_NODENAME -D $PGDATA'
    # unset/cleanup "nss_wrapper" bits
    if [ "${LD_PRELOAD:-}" = '/usr/lib/libnss_wrapper.so' ]; then
        rm -f "$NSS_WRAPPER_PASSWD" "$NSS_WRAPPER_GROUP"
        unset LD_PRELOAD NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP
    fi
}

# append GS_HOST_AUTH_METHOD to pg_hba.conf for "host" connections
function opengauss_setup_hba_conf() {
    
    {
        echo
        if [ 'trust' = "$GS_HOST_AUTH_METHOD" ]; then
            echo '# warning trust is enabled for all connections'
            echo "host    all             all             0.0.0.0/0           md5"
        fi
        if [ -n "$GS_USERNAME" ];then
            echo "host    all             $GS_USERNAME             0.0.0.0/0           md5"
        fi
        if [ -n "$SERVER_MODE" ] && [ -n "$OG_SUBNET" ]; then
            #echo "host    replication     omm        127.0.0.1/32            trust"
            if [ -n "$GS_REPLUSER" ];then
                echo "host    replication     $GS_REPLUSER        $OG_SUBNET            trust"
            else
                echo "host    replication     sufe        $OG_SUBNET            trust"
            fi
        fi
    } >> "$PGDATA/pg_hba.conf"
    #if [ -n "$SERVER_MODE" ] && [ -n "$OG_SUBNET" ]; then
    #    sed -i "/# IPv6 local connections:/a host all omm $OG_SUBNET trust" $PGDATA/pg_hba.conf
    #fi
}

# append parameter to postgres.conf for connections
function opengauss_setup_postgresql_conf() {
    #password_encryption_type = 2           
    #Password storage type, 0 is md5 for PG, 1 is sha256 + md5, 2 is sha256 only
    {
        echo
        if [ -n "$GS_PORT" ]; then
            echo "password_encryption_type = 0"
            echo "port = $GS_PORT"
            echo "wal_level = logical"
        else
            echo '# use default port 5432'
            echo "password_encryption_type = 0"
            echo "wal_level = logical"
        fi
        echo "application_name = '$NODE_NAME'"

        if [ -n "$SERVER_MODE" ]; then
            echo "listen_addresses = '0.0.0.0'"
            echo "most_available_sync = on"
            echo "remote_read_mode = non_authentication"
            echo "pgxc_node_name = '$NODE_NAME'"
            if [ "$SERVER_MODE" = "primary" ]; then
               echo "max_connections = 100"
            else
               echo "max_connections = 100"
            fi
            echo -e "$REPL_CONN_INFO"
            if [ -n "$SYNCHRONOUS_STANDBY_NAMES" ]; then
               echo "synchronous_standby_names=$SYNCHRONOUS_STANDBY_NAMES"
            fi
        else
            echo "listen_addresses = '*'"
        fi

        if [ -n "$OTHER_PG_CONF" ]; then
            echo -e "$OTHER_PG_CONF"
        fi
        echo "modify_initial_password = false"
    } >> "$PGDATA/postgresql.conf"
}

function opengauss_setup_mot_conf() {
    echo "enable_numa = false" >> "$PGDATA/mot.conf"
}

function docker_init_user() {
    query_runner=( gsql -v ON_ERROR_STOP=1 --username "$GS_USER")
    if [ -n "$GS_DB" ]; then
        query_runner+=( --dbname "$GS_DB" )
    else
        GS_USER=$USER
    fi
    if [ -n "$GS_PORT" ]; then
        query_runner+=( --port "$GS_PORT" )
    fi
    echo "initialize password for user $GS_USER"
    echo
    gsql -v ON_ERROR_STOP=1 -d postgres --set user="$GS_USER" <<EOF
        ALTER ROLE :"user" PASSWORD $GS_PASSWORD;
EOF
}

function docker_setup_user() {
    query_runner=( gsql -v ON_ERROR_STOP=1 --username "$GS_USER" --password "$GS_PASSWORD")
    if [ -n "$GS_DB" ]; then
        query_runner+=( --dbname "$GS_DB" )
    fi
    if [ -n "$GS_PORT" ]; then
        query_runner+=( --port "$GS_PORT" )
    fi
    if [ -n "$GS_USERNAME" ]; then
        $query_runner --dbname postgres --set user="$GS_USERNAME" <<EOF
        create user :"user" SYSADMIN REPLICATION password $GS_PASSWORD;
EOF
    else
        echo " default user is $GS_USER"
    fi
}

function docker_setup_rep_user() {
    if [ -n "$SERVER_MODE" ] && [ "$SERVER_MODE" = "primary" ]; then
        GS_DB= docker_process_sql --dbname postgres --set passwd="$GS_PASSWORD" --set user="${GS_REPLUSER}" <<-'EOSQL'
        create user :"user" SYSADMIN REPLICATION password :"passwd" ;
EOSQL
    else
        echo " default no repuser created"
    fi
}

# start socket-only postgresql server for setting up or running scripts
# all arguments will be passed along as arguments to `postgres` (via pg_ctl)
function docker_start_server() {
    if [ "$1" = 'gaussdb' ]; then
        shift
    fi
    mkdir ${PGHOST}
    #chmod 777 ${PGDATA}/tmp 
    PGUSER="${PGUSER:-$GS_USER}" \
    gs_ctl -D "$PGDATA" \
            -w start 
    #rm -rf ${PGDATA}/tmp
}

# stop postgresql server after done setting up user and running scripts
function docker_stop_server() {
    PGUSER="${PGUSER:-postgres}" \
    gs_ctl -D "$PGDATA" -m fast -w stop
}

function set_user_passwd() {
    echo "${GS_PASSWORD}" | passwd $USER --stdin
}

############################################
## Init OpenGauss Cluster with raw method ##
############################################

function opengauss_setup_cluster_conf() {
    correct_network_ip=$(ip route show |grep -v default | grep $(ip route show | grep default | awk '{print $5}') | awk '{print $9}')
    host_array=(${PRIMARYHOST})
    standby_hosts=${STANDBYHOSTS}
    host_array+=(${standby_hosts//[,\"]/ })
    echo "Cluster nodes: ${host_array[@]}"
    # It needs to enhance capture $correct_network_ip logic, if the host has 2 network interfaces.
    
    index=1
    if [ ${PRIMARYHOST} == ${correct_network_ip} ];then 
    for ((i = 0; i < ${#host_array[@]}; i++)); do
        if [ "${correct_network_ip}" != "${host_array[i]}" ]; then
            echo "replconninfo${index}='localhost=${correct_network_ip} localport=5433 localheartbeatport=5436 remotehost=${host_array[i]} remoteport=5433 remoteheartbeatport=5436'"
            gs_guc set -D ${PGDATA} -c "replconninfo${index}='localhost=${correct_network_ip} localport=5433 localheartbeatport=5436 remotehost=${host_array[i]} remoteport=5433 remoteheartbeatport=5436'"
            index=$(expr $index + 1)
        fi
        gs_guc set -D ${PGDATA} -h "host    all             $GS_USER             ${host_array[i]}/32           trust"
    done
    else
        echo "replconninfo${index}='localhost=${correct_network_ip} localport=5433 localheartbeatport=5436 remotehost=${PRIMARYHOST} remoteport=5433 remoteheartbeatport=5436'"
        gs_guc set -D ${PGDATA} -c "replconninfo${index}='localhost=${correct_network_ip} localport=5433 localheartbeatport=5436 remotehost=${PRIMARYHOST} remoteport=5433 remoteheartbeatport=5436'"
    fi

    if [ ${#host_array[@]} -eq 2 ]; then
        echo "Master-slave cluster with 2 openGauss instances, set sync_most_available=on." 
        gs_guc set -D ${PGDATA} -c "most_available_sync=on"
    fi
}


# IPv4 local connections:
#host    all             all             127.0.0.1/32            trust
#host    all             omm             172.11.0.2/32           trust
#host    all             omm             172.11.0.3/32           trust
#host    all             hbu             0.0.0.0/0               md5

function docker_check_primary() {
    #local query_runner=(gsql -d "host=$PRIMARYHOST port=${GS_PORT} dbname=postgres")
    if [ -z "$GS_PORT" ]; then
        GS_PORT=5432
    fi
    set +e
    echo
    echo "Deplay ${max_delay_primary_interval} seconds to check openGauss primary instance status."
    echo 
    sleep $max_delay_primary_interval
    echo "Start to check openGauss primary instance status at host:  ${PRIMARYHOST}..."
    echo 
    is_primary_running=0
    for ((i=1;i<=max_retry_primary_num;i++)); do
        echo "Check openGauss primary instance status with loop number=$i"
        QUERY_PRIMARY_REUSLT=$(gsql -v ON_ERROR_STOP=1 -d "host=$PRIMARYHOST port=${GS_PORT} dbname=postgres user=$GS_USERNAME password=${GS_PASSWORD}" -c "SELECT name, setting FROM pg_settings where name like 'replconninfo%';")
        if [ -z "$QUERY_PRIMARY_REUSLT" ];then
            sleep ${max_retry_primary_interval}
        else
            if [ $(echo "$QUERY_PRIMARY_REUSLT" |grep $PRIMARYHOST | grep replconninfo | wc -l) -gt 0 ]; then
                echo "$QUERY_PRIMARY_REUSLT" 
                is_primary_running=$(expr $is_primary_running + 1)
            fi 
            # introduce double check mechanism
            if [ $is_primary_running -gt 1 ];then
                echo "Double check openGauss primary instance status, and it is running at host: ${PRIMARYHOST}..."
                echo
                break
            else
                sleep ${max_retry_primary_interval} 
            fi
        fi
        echo
    done 
    set -e
        
}

function docker_slave_full_backup() {
        if [ "$SERVER_MODE" = "standby" ];then
            gs_ctl build -D "$PGDATA" -b full -M standby
        else
            gs_ctl build -D "$PGDATA" -b full
        fi
}

function main() {
    # if first arg looks like a flag, assume we want to run postgres server
    if [ "${1:0:1}" = '-' ]; then
        set -- gaussdb "$@"
    fi
    if [ "$1" = 'gaussdb' ] && ! _opengauss_want_help "$@"; then
        set_envfile 
        docker_setup_env
        # setup data directories and permissions (when run as root)
            
        # only run initialization on an empty data directory
        if [ -z "$DATABASE_ALREADY_EXISTS" ]; then
        
            docker_verify_minimum_env
            # check dir permissions to reduce likelihood of half-initialized database
            # ls /docker-entrypoint-initdb.d/ > /dev/null
         
            docker_init_database
            opengauss_setup_hba_conf
            opengauss_setup_postgresql_conf
            if [ $GAUSS_EDITION != "lite" ];then
                opengauss_setup_mot_conf
            fi
         
            # PGPASSWORD is required for gsql when authentication is required for 'local' connections via pg_hba.conf and is otherwise harmless
            # e.g. when '--auth=md5' or '--auth-local=md5' is used in POSTGRES_INITDB_ARGS
            export PGPASSWORD="${PGPASSWORD:-$GS_PASSWORD}"
            echo "Generate openGuassdb configuration succesfully."
            docker_start_server "$@" 
            
            docker_init_user
            if [ -z "$SERVER_MODE" ] || [ "$SERVER_MODE" = "primary" ]; then
                echo
                echo "Setup user and database with mode=\"$SERVER_MODE\""
                echo
                docker_setup_user
                if [ -n "${GS_REPLUSER}" ];then
                    docker_setup_rep_user
                fi
                # docker_setup_db
                # docker_setup_slot
                # docker_process_init_files /docker-entrypoint-initdb.d/*
            fi
            if [ -n "$STANDBYHOSTS" ]; then
                opengauss_setup_cluster_conf
            fi
            if [ "$SERVER_MODE" == "standby" ];then
                docker_check_primary
            fi
            #exit 
            if [ -n "$SERVER_MODE" ] && [ "$SERVER_MODE" != "primary" ]; then
                docker_stop_server
                docker_slave_full_backup
            fi
            docker_stop_server
            unset PGPASSWORD
            echo
            echo 'openGauss init process complete; ready for start up.'
            echo
        else
            echo
            echo 'openGauss Database directory appears to contain a database; Skipping initialization'
            echo
            if [ "$SERVER_MODE" == "standby" ];then
                echo
                echo "build openGauss standby instance after docker container restarted"
                echo
                docker_check_primary
                docker_slave_full_backup
                docker_stop_server
            fi

        fi
    fi
    exec "$@"
}

if ! _is_sourced; then
    main "$@"
fi
